import hashlib
import json
import time
import random
import string
import requests
import xml.etree.ElementTree as ET
from datetime import datetime
import logging

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

# 全局配置缓存
_wxpay_config_cache = None
_wxpay_config_last_fetched = 0
CONFIG_CACHE_TTL = 300  # 5分钟缓存
    
def get_wxpay_config():
    """
    获取微信支付配置信息
    """
    global _wxpay_config_cache, _wxpay_config_last_fetched
    current_time = time.time()

    if _wxpay_config_cache and (current_time - _wxpay_config_last_fetched < CONFIG_CACHE_TTL):
        logger.debug("使用缓存的微信支付配置")
        return _wxpay_config_cache

    logger.info("加载微信支付配置...")
    
    # 微信支付配置
    _wxpay_config_cache = {
        'appid': 'wxc336e9fdfca24214',  # 小程序AppID
        'mch_id': '1729006342',  # 商户号
        'mch_key': 'Ysei9UVbq8g8EhHvK5pYYWlTrvz7vxdb',  # 商户APIv2密钥
        'app_secret': 'a04c40b3d4764489c0fca3f4bffb9b31',  # 小程序秘钥
        'notify_url': 'https://api.playtruly.cn/api/payment/wxpay/notify/',  # ✅ 线上异步通知URL（必须HTTPS）
        'trade_type': 'JSAPI',  # 交易类型：JSAPI-小程序支付
        'gateway_url': 'https://api.mch.weixin.qq.com/pay/unifiedorder',  # 统一下单接口
        'query_url': 'https://api.mch.weixin.qq.com/pay/orderquery',  # 订单查询接口
        'refund_url': 'https://api.mch.weixin.qq.com/secapi/pay/refund',  # 退款接口
         
    }

    _wxpay_config_last_fetched = current_time
    logger.info("微信支付配置加载成功")
    return _wxpay_config_cache


def generate_nonce_str(length=32):
    """
    生成随机字符串
    """
    return ''.join(random.choices(string.ascii_letters + string.digits, k=length))


def generate_sign(params, mch_key, sign_type='MD5'):
    """
    生成微信支付签名
    
    Args:
        params: 参数字典
        mch_key: 商户密钥
        sign_type: 签名类型，默认MD5
    
    Returns:
        签名字符串
    """
    # 过滤空值和sign字段
    filtered_params = {k: v for k, v in params.items() if v != '' and k != 'sign'}
    
    # 按参数名ASCII字典序排序
    sorted_params = sorted(filtered_params.items())
    
    # 拼接参数字符串
    param_str = '&'.join([f"{k}={v}" for k, v in sorted_params])
    
    # 添加商户密钥
    string_to_sign = f"{param_str}&key={mch_key}"
    
    logger.debug(f"待签名字符串: {string_to_sign}")
    
    # 计算签名
    if sign_type == 'MD5':
        sign = hashlib.md5(string_to_sign.encode('utf-8')).hexdigest().upper()
    elif sign_type == 'HMAC-SHA256':
        import hmac
        sign = hmac.new(mch_key.encode('utf-8'), string_to_sign.encode('utf-8'), hashlib.sha256).hexdigest().upper()
    else:
        raise ValueError(f"不支持的签名类型: {sign_type}")
    
    logger.debug(f"生成的签名: {sign}")
    return sign


def verify_sign(params, mch_key, sign_type='MD5'):
    """
    验证微信支付签名
    
    Args:
        params: 参数字典（包含sign字段）
        mch_key: 商户密钥
        sign_type: 签名类型
    
    Returns:
        bool: 验证结果
    """
    if 'sign' not in params:
        logger.warning("参数中没有sign字段")
        return False
    
    received_sign = params['sign']
    calculated_sign = generate_sign(params, mch_key, sign_type)
    
    if received_sign == calculated_sign:
        logger.info("签名验证成功")
        return True
    else:
        logger.warning(f"签名验证失败: 接收到的签名={received_sign}, 计算的签名={calculated_sign}")
        return False


def dict_to_xml(data):
    """
    将字典转换为XML格式
    """
    xml_str = '<xml>'
    for key, value in data.items():
        xml_str += f'<{key}><![CDATA[{value}]]></{key}>'
    xml_str += '</xml>'
    return xml_str


def xml_to_dict(xml_str):
    """
    将XML格式转换为字典
    """
    try:
        root = ET.fromstring(xml_str)
        result = {}
        for child in root:
            result[child.tag] = child.text
        return result
    except Exception as e:
        logger.error(f"XML解析失败: {str(e)}")
        raise


def get_jsapi_openid(code):
    """
    通过code获取用户的openid
    
    Args:
        code: 小程序登录时获取的code
    
    Returns:
        dict: 包含openid和session_key的字典
    """
    config = get_wxpay_config()
    url = f"https://api.weixin.qq.com/sns/jscode2session"
    
    params = {
        'appid': config['appid'],
        'secret': config['app_secret'],
        'js_code': code,
        'grant_type': 'authorization_code'
    }
    
    try:
        response = requests.get(url, params=params, timeout=10)
        result = response.json()
        
        if 'openid' in result:
            logger.info(f"获取openid成功: {result['openid']}")
            return {
                'openid': result['openid'],
                'session_key': result.get('session_key'),
                'unionid': result.get('unionid')
            }
        else:
            logger.error(f"获取openid失败: {result}")
            raise Exception(f"获取openid失败: {result.get('errmsg', '未知错误')}")
    except Exception as e:
        logger.error(f"请求微信API失败: {str(e)}")
        raise


def create_unified_order(out_trade_no, total_fee, body, openid, spbill_create_ip='127.0.0.1'):
    """
    统一下单接口
    
    Args:
        out_trade_no: 商户订单号
        total_fee: 订单金额（单位：分）
        body: 商品描述
        openid: 用户openid
        spbill_create_ip: 终端IP
    
    Returns:
        dict: 包含prepay_id等信息的字典
    """
    config = get_wxpay_config()
    
    # 构建请求参数
    params = {
        'appid': config['appid'],
        'mch_id': config['mch_id'],
        'nonce_str': generate_nonce_str(),
        'body': body,
        'out_trade_no': out_trade_no,
        'total_fee': str(total_fee),
        'spbill_create_ip': spbill_create_ip,
        'notify_url': config['notify_url'],
        'trade_type': config['trade_type'],
        'openid': openid
    }
    
    # 生成签名
    params['sign'] = generate_sign(params, config['mch_key'])
    
    # 转换为XML
    xml_data = dict_to_xml(params)
    logger.info(f"统一下单请求: {xml_data}")
    
    try:
        # 发送请求
        response = requests.post(
            config['gateway_url'],
            data=xml_data.encode('utf-8'),
            headers={'Content-Type': 'application/xml'},
            timeout=10
        )
        
        logger.info(f"微信支付响应: {response.text}")
        
        # 解析响应
        result = xml_to_dict(response.text)
        
        # 检查返回状态
        if result.get('return_code') != 'SUCCESS':
            raise Exception(f"微信支付接口调用失败: {result.get('return_msg')}")
        
        if result.get('result_code') != 'SUCCESS':
            raise Exception(f"微信支付下单失败: {result.get('err_code_des', result.get('err_code'))}")
        
        # 验证签名
        if not verify_sign(result, config['mch_key']):
            raise Exception("微信支付响应签名验证失败")
        
        logger.info(f"统一下单成功，prepay_id: {result.get('prepay_id')}")
        return result
        
    except Exception as e:
        logger.error(f"统一下单失败: {str(e)}")
        raise


def generate_jsapi_pay_params(prepay_id):
    """
    生成小程序调起支付所需的参数
    
    Args:
        prepay_id: 预支付交易会话标识
    
    Returns:
        dict: 小程序支付参数
    """
    config = get_wxpay_config()
    
    timestamp = str(int(time.time()))
    nonce_str = generate_nonce_str()
    
    # 构建支付参数
    pay_params = {
        'appId': config['appid'],
        'timeStamp': timestamp,
        'nonceStr': nonce_str,
        'package': f'prepay_id={prepay_id}',
        'signType': 'MD5'
    }
    
    # 生成paySign
    pay_params['paySign'] = generate_sign(pay_params, config['mch_key'])
    
    logger.info("生成小程序支付参数成功")
    return pay_params


def query_order(out_trade_no=None, transaction_id=None):
    """
    查询订单
    
    Args:
        out_trade_no: 商户订单号
        transaction_id: 微信订单号（二选一）
    
    Returns:
        dict: 订单信息
    """
    config = get_wxpay_config()
    
    if not out_trade_no and not transaction_id:
        raise ValueError("out_trade_no和transaction_id至少提供一个")
    
    # 构建请求参数
    params = {
        'appid': config['appid'],
        'mch_id': config['mch_id'],
        'nonce_str': generate_nonce_str()
    }
    
    if out_trade_no:
        params['out_trade_no'] = out_trade_no
    if transaction_id:
        params['transaction_id'] = transaction_id
    
    # 生成签名
    params['sign'] = generate_sign(params, config['mch_key'])
    
    # 转换为XML
    xml_data = dict_to_xml(params)
    logger.info(f"查询订单请求: {xml_data}")
    
    try:
        # 发送请求
        response = requests.post(
            config['query_url'],
            data=xml_data.encode('utf-8'),
            headers={'Content-Type': 'application/xml'},
            timeout=10
        )
        
        logger.info(f"查询订单响应: {response.text}")
        
        # 解析响应
        result = xml_to_dict(response.text)
        
        # 检查返回状态
        if result.get('return_code') != 'SUCCESS':
            raise Exception(f"微信支付接口调用失败: {result.get('return_msg')}")
        
        if result.get('result_code') != 'SUCCESS':
            raise Exception(f"查询订单失败: {result.get('err_code_des', result.get('err_code'))}")
        
        # 验证签名
        if not verify_sign(result, config['mch_key']):
            raise Exception("微信支付响应签名验证失败")
        
        logger.info(f"查询订单成功，交易状态: {result.get('trade_state')}")
        return result
        
    except Exception as e:
        logger.error(f"查询订单失败: {str(e)}")
        raise


def parse_notify_data(xml_data):
    """
    解析微信支付异步通知数据
    
    Args:
        xml_data: XML格式的通知数据
    
    Returns:
        dict: 解析后的数据字典
    """
    config = get_wxpay_config()
    
    try:
        # 解析XML
        result = xml_to_dict(xml_data)
        
        logger.info(f"收到微信支付通知: {result}")
        
        # 检查返回状态
        if result.get('return_code') != 'SUCCESS':
            raise Exception(f"微信通知返回失败: {result.get('return_msg')}")
        
        # 验证签名
        if not verify_sign(result, config['mch_key']):
            raise Exception("微信支付通知签名验证失败")
        
        logger.info("微信支付通知签名验证成功")
        return result
        
    except Exception as e:
        logger.error(f"解析微信支付通知失败: {str(e)}")
        raise


def generate_notify_response(return_code='SUCCESS', return_msg='OK'):
    """
    生成微信支付通知响应
    
    Args:
        return_code: 返回状态码
        return_msg: 返回信息
    
    Returns:
        str: XML格式的响应
    """
    response_data = {
        'return_code': return_code,
        'return_msg': return_msg
    }
    return dict_to_xml(response_data)


def apply_refund(out_trade_no, out_refund_no, total_fee, refund_fee, refund_desc=''):
    """
    申请退款（需要证书，暂时不实现完整功能）
    
    Args:
        out_trade_no: 商户订单号
        out_refund_no: 商户退款单号
        total_fee: 订单总金额（单位：分）
        refund_fee: 退款金额（单位：分）
        refund_desc: 退款原因
    
    Returns:
        dict: 退款结果
    """
    config = get_wxpay_config()
    
    # 构建请求参数
    params = {
        'appid': config['appid'],
        'mch_id': config['mch_id'],
        'nonce_str': generate_nonce_str(),
        'out_trade_no': out_trade_no,
        'out_refund_no': out_refund_no,
        'total_fee': str(total_fee),
        'refund_fee': str(refund_fee),
        'refund_desc': refund_desc
    }
    
    # 生成签名
    params['sign'] = generate_sign(params, config['mch_key'])
    
    logger.warning("退款接口需要商户证书，请配置证书路径后使用")
    # 注意：退款接口需要使用商户证书进行双向认证
    # 需要配置cert和key文件路径
    raise NotImplementedError("退款功能需要配置商户证书")


